﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using System.Collections;

namespace FUSE
{



        /// <summary>
        /// NOTE: please use descriptive names instead of gc use galaxyComponent, or gravMap vs gravityMap
        /// The extra characters will help undertand the function instantly
        /// also please do not commit broken code
        /// please create a contract to your function before committing
        /// please declare all your variables at the top of the class or function
        /// </summary>
        public static class PhysicsEngine
        {
            public static int k_looks_good = 7;
            
            /// <summary>
            /// Generates a new gravity map
            /// </summary>
            /// <param name="galaxy"></param>
            /// NOTE: Think about making gravityGrid be type GenericForces[,] this was we can have child SlingForces(for slingOrbit) CrashFroces(for CrashOrbit)
            /// NOTE: Needs to be tested
            /// <returns></returns>
            public static Vector2[,] createGravityGrid2(Galaxy galaxy)
            {
                
                int planetMass;
                //double crashRadius; //not needed here
                double slingRadius;

                int centerX;
                int centerY;

                double maxY;
                int maxX;


                int xSize = galaxy.getXSize();
                int ySize = galaxy.getYSize();

                Vector2[,] newGravityMap = null;


                //Create gravity grid
                newGravityMap = new Vector2[xSize,ySize];

                //For every object with mass in the galaxy table create a slingshot gravity area
                //also a crash gravity area
                foreach (DictionaryEntry entry in galaxy.GalaxyComponentsTable)
                {
                    Console.Write("Before GRID Create");
                    Debug.Assert(entry.Key != null);
                    if (entry.Value is IMassedComponent)
                    {
                        Console.Write("IN GRID Create\n\n\n");
                        IMassedComponent massedComponent = (IMassedComponent)entry.Value;

                        //crashRadius = massedComponent.CrashCircle.radius;
                        slingRadius = massedComponent.SlingCircle.radius;

                        //Maximum x and y possible according to radius
                        maxY = (int) slingRadius;
                        maxX = (int) slingRadius;

                        //X and Y counters we will be using to mark inside of orbit
                        int xCounter = maxX;
                        int yCounter = 0;

                        //Massed component is planet
                        planetMass = massedComponent.Mass;

                        //Find slingshot orbit center(x,y) of this massed component 
                        centerX = (int) massedComponent.SlingCircle.centerPOS.X;
                        centerY = (int) massedComponent.SlingCircle.centerPOS.Y;

                        //Only calculate 90% and reflect it for other 3 quadrents ie 270%
                        while (xCounter >= 0 && yCounter <= maxY) //start at the right when x=maxX and y = 0;
                        {
                            //if the product of the x and y coordinates are less than the radius
                            if (Math.Sqrt(xCounter * xCounter + yCounter * yCounter) <= slingRadius)
                            {
                                //mark all squares from the center up to x
                                //NOTE: only Squares outside the crash radius needs to be marked, so we can detect its in either crash or sling orbits
                                /**
                                 *    ***
                                 *    ******
                                 *    ********
                                 *  ()********
                                 */
                                for (int x = 0; x <= xCounter; x++)
                                {
                                    markGravityMapGrid(x, yCounter, massedComponent, newGravityMap, galaxy); //Mark up to x only
                                }
                                //move up one y do it again
                                Console.Out.WriteLine("Incremented Y "+ yCounter);
                                yCounter++;
                            }
                            else                             //move to the left 1 x try again
                            {
                                System.Console.Out.WriteLine("MarkGrid MOVING TO NEXT LINE x_ "+ xCounter);
                                xCounter--;
                            }
                           

                        }

                    }

                }

                return newGravityMap;

            }
            /// <summary>
            /// Mark the global forces on the Gravity map grid, by reflecting for each quadrant
            /// </summary>
            /// <param name="xRelative"></param> x position from the origin being centre
            /// <param name="yRelative"></param> y position from the origin being centre
            /// <param name="centerX"></param> x center of the massed object
            /// <param name="centerY"></param> y center of the massed object
            private static void markGravityMapGrid(int xRelative, int yRelative, IMassedComponent massedObject, Vector2[,] gravityMap, Galaxy galaxy)
            {
                Vector2 gravity;
                int xGlobal;
                int yGlobal;
                try
                {

                    //Top right quadrant
                    //Find the x and y global to the grid
                    xGlobal = xRelative + (int)massedObject.SlingCircle.centerPOS.X;
                    yGlobal = yRelative + (int)massedObject.SlingCircle.centerPOS.Y;

                    gravity = calculateGravityForces(xRelative, yRelative, massedObject.Mass);
                    gravityMap[xGlobal, yGlobal] = gravity;
                    
                    //Top left quadrant(Bottom left in pic)
                    //Find the x and y global to the grid
                    xGlobal = -xRelative + (int)massedObject.SlingCircle.centerPOS.X;
                    yGlobal = yRelative + (int)massedObject.SlingCircle.centerPOS.Y;


                    gravityMap[xGlobal, yGlobal] = gravity;
                    
                    //Bottom right quadrant
                    //Find the x and y global to the grid
                    xGlobal = xRelative + (int)massedObject.SlingCircle.centerPOS.X;
                    yGlobal = -yRelative + (int)massedObject.SlingCircle.centerPOS.Y;


                    gravityMap[xGlobal, yGlobal] = gravity;
                    
                    //Bottom left quadrant
                    //Find the x and y global to the grid
                    xGlobal = -xRelative + (int)massedObject.SlingCircle.centerPOS.X;
                    yGlobal = -yRelative + (int)massedObject.SlingCircle.centerPOS.Y;

                    gravityMap[xGlobal, yGlobal] = gravity;
                     
                }
                catch (IndexOutOfRangeException)
                {
                    //handle exception
                    throw;
                }


            }

            /// <summary>
            /// Calculates the gravity forces based on k_looks_good
            /// </summary>
            /// <param name="xCounter"></param>
            /// <param name="yCounter"></param>
            /// <param name="mass"></param>
            /// 
            /// <returns> A gravity x and y force vector</returns>
            private static Vector2 calculateGravityForces(int xCounter, int yCounter, double mass)
            {

                double force = (PhysicsEngine.k_looks_good * mass);///(xCounter*xCounter + yCounter*yCounter);

                double theta = Math.Atan2(yCounter, xCounter);
                //test
                double xForce = force*Math.Cos(theta);
                double yForce = force*Math.Sin(theta);

                return new Vector2((float)xForce, (float)yForce);

            }


            public static Vector2[,] createGravityGrid(Galaxy galaxy)
            {

                double distance;
                int planetMass;
                //double crashRadius; //not needed here
                double slingRadius;

                int leftX;
                int rightX;

                int centerX;
                int centerY;

                double k_looksgood = 7;
                double theta;
                double force;

                double xForce;
                double yForce;

                Vector2 currentVector;

                int xSize = galaxy.getXSize();
                int ySize = galaxy.getYSize();

                Vector2[,] newGravityMap = null;


                //Create gravity grid
                newGravityMap = new Vector2[xSize, ySize];

                //For every object with mass in the galaxy table create a slingshot gravity area
                //also a crash gravity area
                foreach (GalaxyComponent galaxyComponent in galaxy.GalaxyComponentsTable)
                {
                    if (galaxyComponent is IMassedComponent)
                    {
                        IMassedComponent massedComponent = (IMassedComponent)galaxyComponent;

                        //crashRadius = massedComponent.CrashCircle.radius;
                        slingRadius = massedComponent.SlingCircle.radius;

                        //Massed component is planet
                        planetMass = massedComponent.Mass;

                        //Find slingshot orbit center(x,y) of this massed component 
                        centerX = (int)massedComponent.SlingCircle.centerPOS.X;
                        centerY = (int)massedComponent.SlingCircle.centerPOS.Y;

                        //Do we have to go 360% we could go 90% and reflect each coordinate
                        for (int ycounter = (int)slingRadius; ycounter >= -slingRadius; ycounter--) //start at the top of the circle and move down
                        {

                            //e.g. slingRadius= 5, centerX= 20, yCounter=slingRadius
                            //sqrt(25-(400)-(25)) = 20imaginary??
                            rightX = (int)Math.Sqrt(slingRadius * slingRadius - centerX * centerX - ycounter * ycounter);    //get the x range for each new row
                            leftX = -rightX;

                            for (int xcounter = leftX; xcounter <= rightX; xcounter++)  //start at the left of the row and move right
                            {
                                distance = Math.Sqrt((ycounter - centerY) * (ycounter - centerY) + (xcounter - centerX) * (xcounter - centerX));

                                force = k_looksgood * planetMass / (distance * distance);
                                theta = Math.Atan2(ycounter - centerY, xcounter - centerX);

                                xForce = force * Math.Sin(theta);
                                yForce = force * Math.Cos(theta);
                                currentVector.X = newGravityMap[xcounter, ycounter].X + (float)xForce;
                                currentVector.Y = newGravityMap[xcounter, ycounter].Y + (float)yForce;

                                newGravityMap[xcounter, ycounter] = currentVector;

                            }

                        }

                    }

                }

                return newGravityMap;

            }
            public static String ToStringGalaxy(Galaxy galaxy, Boolean verbose)
            {
                String rtString = "";
                Vector2 squareForce;

                int xSize = galaxy.getXSize();
                int ySize = galaxy.getYSize();
                int x = 0;

                for (int y = 0; y < xSize; y++)
                {

                    for (x = 0; x < ySize; x++)
                    {


                        squareForce =galaxy.GalaxyForces[x, y];

                        if (verbose == true && (squareForce.X > 0 || squareForce.Y > 0))
                        {
                            rtString += "[(" + x + "," + y + ")" + "F: (" + squareForce.X + "," + squareForce.Y + ")]" +
                                        " ";
                        }
                        else if (verbose == false && (squareForce.X > 0 || squareForce.Y > 0))
                        {
                            rtString += "*";
                        }
                        else
                        {
                            rtString += "0";

                        }
                    }
                    rtString += "\n";
                }
                
                return rtString;
            }
 
            public static void main(string[] args)
            {
                Galaxy testGalaxy = new Galaxy();

                testGalaxy.Initialize();

                //String printString = PhysicsEngine.ToStringGalaxy(testGalaxy);
                //System.Console.Write(printString);
            }
        
    }

}
